import { GeometryShape, GeometryType, Pointer } from '@/graph/3d/config/shapes'
import * as THREE from 'three'
import { PointerGenerator } from '../usePointer'
import { MathUtils } from '@/graph/3d/utils'
import { createParametricGeometry, disposeGeometry } from '@/utils/threeUtils'
import { compile } from 'mathjs'
import { elevationMaterial } from '@/utils/materials'

type CreateParams = Pointer[]

class Base implements GeometryShape {
  id: string
  formulaId: string = ''
  name: string = ''
  type: GeometryType = GeometryType.Function
  createMinSteps: number = 0
  position: VerticesType = [0, 0, 0]
  rotation: VerticesType = [0, 0, 0]
  scale: VerticesType = [1, 1, 1]
  maxSteps: number = 0
  ref = null
  vertices: VerticesType[] = []
  lines: [VerticesType, VerticesType][] = []
  faces: THREE.Mesh[] = []
  constructor(shape?: Partial<GeometryShape>) {
    Object.assign(this, shape)
    const uuid = THREE.MathUtils.generateUUID()
    this.id = 'shape' + uuid
  }
  create(pointers: CreateParams): Base {
    return this
  }
}

export class Cube extends Base {
  maxSteps: number = 2
  name: string = '正六面体'
  type: GeometryType = GeometryType.Cube
  create(pointers: CreateParams): Cube {
    const [v1, v2] = pointers

    const p1 = new THREE.Vector3().fromArray(v1.position)
    const p2 = new THREE.Vector3().fromArray(v2.position)

    const distance = p1.distanceTo(p2)

    const p3 = MathUtils.rotateAroundCenter(p1, p2, -Math.PI / 2, 'Z')
    p3.z = p2.z

    const plane = new THREE.Plane()
    plane.setFromCoplanarPoints(p1, p2, p3)
    const quaternion = new THREE.Quaternion().setFromUnitVectors(
      new THREE.Vector3(0, 0, 1),
      plane.normal,
    )
    const euler = new THREE.Euler().setFromQuaternion(quaternion)

    const AB = new THREE.Vector3().subVectors(p1, p2)
    const AC = new THREE.Vector3().subVectors(p3, p2)
    const AD = AC.clone().add(AB)
    const p4 = new THREE.Vector3().addVectors(p2, AD)

    const v3 = new PointerGenerator({
      position: p3.toArray(),
      canMove: true,
    })
    const v4 = new PointerGenerator({ position: p4.toArray() })

    const [v5, v6, v7, v8] = (() => {
      // 计算底面的法向量
      const AB = new THREE.Vector3().subVectors(p2, p1)
      const AD = new THREE.Vector3().subVectors(p4, p1)
      const normal = new THREE.Vector3().crossVectors(AB, AD).normalize()

      const offset = normal.clone().multiplyScalar(distance)
      const [A, B, C, D] = [p1, p2, p3, p4].map((p) => {
        const _p = new THREE.Vector3().addVectors(p, offset)
        return new PointerGenerator({ position: _p.toArray() })
      })

      return [A, B, C, D]
    })()

    const vertices = [v1, v2, v3, v4, v5, v6, v7, v8]
    const edges = [
      [0, 1],
      [1, 2],
      [2, 3],
      [3, 0], // 底面
      [4, 5],
      [5, 6],
      [6, 7],
      [7, 4], // 顶面
      [0, 4],
      [1, 5],
      [2, 6],
      [3, 7], // 侧面
    ]

    this.position = this.getCenter(vertices)
    this.rotation = euler.toArray() as VerticesType
    const vCenter = new PointerGenerator({ position: this.position })
    const w = distance / 2,
      o = this.position
    const v1Origin = new PointerGenerator({
      position: [o[0] - w, o[1] - w, o[2] - w],
    })
    const v8Origin = new PointerGenerator({
      position: [o[0] + w, o[1] + w, o[2] + w],
    })

    this.vertices = [
      ...vertices.map((v) => v.position),
      vCenter.position,
      v1Origin.position,
      v8Origin.position,
    ]

    const cube = new THREE.Mesh(
      new THREE.BoxGeometry(distance, distance, distance),
      new THREE.MeshBasicMaterial({ color: 0xffffff }),
    )

    cube.position.fromArray(this.position)
    cube.scale.fromArray(this.scale)
    cube.rotation.fromArray(this.rotation)

    const lines: [VerticesType, VerticesType][] = []
    edges.forEach((edge) => {
      const [i1, i2] = edge
      const v1 = vertices[i1]
      const v2 = vertices[i2]
      const p1 = cube
        .worldToLocal(new THREE.Vector3().fromArray(v1.position))
        .toArray()
      const p2 = cube
        .worldToLocal(new THREE.Vector3().fromArray(v2.position))
        .toArray()

      lines.push([p1, p2])
    })

    const l1 = cube
      .worldToLocal(new THREE.Vector3().fromArray(v1Origin.position))
      .toArray()
    const l2 = cube
      .worldToLocal(new THREE.Vector3().fromArray(v8Origin.position))
      .toArray()
    lines.push([l1, l2])

    this.lines = lines

    return this
  }
  getCenter(vertices: Pointer[]): VerticesType {
    const sum: any = vertices.reduce(
      // @ts-ignore
      (acc, vertex) => {
        return [
          acc[0] + vertex.position[0],
          acc[1] + vertex.position[1],
          acc[2] + vertex.position[2],
        ]
      },
      [0, 0, 0] as VerticesType,
    )

    const center: VerticesType = [
      sum[0] / vertices.length,
      sum[1] / vertices.length,
      sum[2] / vertices.length,
    ]

    return center
  }
}

export class Plane extends Base {
  name: string = '三点平面'
  type: GeometryType = GeometryType.Plane
  length: number = 0
  create(pointers: CreateParams): Plane {
    const [v1, v2, v3] = pointers

    const p1 = new THREE.Vector3().fromArray(v1.position)
    const p2 = new THREE.Vector3().fromArray(v2.position)
    const p3 = new THREE.Vector3().fromArray(v3.position)

    const plane = new THREE.Plane()
    plane.setFromCoplanarPoints(p1, p2, p3)
    const quaternion = new THREE.Quaternion().setFromUnitVectors(
      new THREE.Vector3(0, 0, 1),
      plane.normal,
    )
    const euler = new THREE.Euler().setFromQuaternion(quaternion)

    this.position = v1.position
    this.rotation = euler.toArray() as VerticesType

    this.length = p1.distanceTo(p2)
    this.vertices = pointers.map((v) => v.position)

    return this
  }
}

export class Sphere extends Base {
  name: string = '球面'
  type: GeometryType = GeometryType.Sphere
  radius: number = 0
  create(pointers: CreateParams): Sphere {
    const [v1, v2] = pointers

    const p1 = new THREE.Vector3().fromArray(v1.position)
    const p2 = new THREE.Vector3().fromArray(v2.position)

    this.radius = p1.distanceTo(p2)
    this.vertices = pointers.map((v) => v.position)
    this.position = v1.position

    return this
  }
}

export class Line extends Base {
  name: string = '线段'
  type: GeometryType = GeometryType.Line
  create(pointers: CreateParams): Line {
    const [v1, v2] = pointers

    const p1 = new THREE.Vector3().fromArray(v1.position)
    const p2 = new THREE.Vector3().fromArray(v2.position)
    const dir = new THREE.Vector3().subVectors(p2, p1).normalize()
    p1.sub(dir.clone().multiplyScalar(400))
    p2.add(dir.clone().multiplyScalar(400))

    this.vertices = [p1, p2].map((v) => v.toArray())
    this.position = v1.position

    return this
  }
}

export class Vector extends Base {
  name: string = '向量'
  type: GeometryType = GeometryType.Vector
  direction: VerticesType = [0, 0, 0]
  length: number = 0
  create(pointers: CreateParams): Vector {
    const p1 = new THREE.Vector3()
    const p2 = new THREE.Vector3()

    if (pointers.length === 1) {
      p2.fromArray(pointers[0].position)
    } else {
      p1.fromArray(pointers[0].position)
      p2.fromArray(pointers[1].position)
    }

    this.direction = new THREE.Vector3()
      .subVectors(p2, p1)
      .normalize()
      .toArray()
    this.length = p1.distanceTo(p2)
    this.vertices = [p1, p2].map((v) => v.toArray())

    return this
  }
}

export class Function extends Base {
  name: string = '函数'
  type: GeometryType = GeometryType.Function
  geometry: THREE.BufferGeometry | undefined
  material: THREE.Material | undefined
  formulaStr: string = ''
  content: string = ''
  axisName: 'x' | 'y' | 'z' = 'z' // 默认使用Z轴作为高度轴
  width: number = 20
  height: number = 20
  constructor(options?: Partial<Function>) {
    super(options)
    this.geometry = undefined
    this.material = undefined
    this.formulaStr = options?.formulaStr || ''
    const [axisName, content] = this.formulaStr.split('=').map((v) => v.trim())
    this.axisName = (axisName || 'z') as 'x' | 'y' | 'z'
    this.content = content || ''

    console.log('Function constructor', content, this.axisName)

    const compiledFn = compile(content)
    // 根据轴向选择参数化方式
    const parametricFn = this.getParametricFunction(compiledFn)
    if (this.geometry) {
      disposeGeometry(this.geometry)
    }
    this.geometry = createParametricGeometry(parametricFn)
    this.material = new THREE.MeshNormalMaterial({ side: THREE.DoubleSide })
  }
  create(pointers: CreateParams): Function {
    return this
  }
  private getParametricFunction(compiledFn: any) {
    const w = this.width,
      h = this.height
    switch (this.axisName) {
      case 'x':
        return (u: number, v: number) => {
          const y = u * w - w / 2
          const z = v * h - h / 2
          const x = compiledFn.evaluate({ y, z })
          return [x, y, z]
        }
      case 'y':
        return (u: number, v: number) => {
          const x = u * w - w / 2
          const z = v * h - h / 2
          const y = compiledFn.evaluate({ x, z })
          return [x, y, z]
        }
      case 'z':
      default:
        return (u: number, v: number) => {
          const x = u * w - w / 2
          const y = v * h - h / 2
          const z = compiledFn.evaluate({ x, y })
          return [x, y, z]
        }
    }
  }
}

const shapeMap: Record<GeometryType, typeof Base | undefined> = {
  [GeometryType.Cube]: Cube,
  [GeometryType.Sphere]: Sphere,
  [GeometryType.Cylinder]: undefined,
  [GeometryType.Cone]: undefined,
  [GeometryType.Torus]: undefined,
  [GeometryType.TorusKnot]: undefined,
  [GeometryType.Plane]: Plane,
  [GeometryType.Circle]: undefined,
  [GeometryType.Arrow]: undefined,
  [GeometryType.Arc]: undefined,
  [GeometryType.Line]: Line,
  [GeometryType.Rect]: undefined,
  [GeometryType.Ellipse]: undefined,
  [GeometryType.Polygon]: undefined,
  [GeometryType.Text]: undefined,
  [GeometryType.Sprite]: undefined,
  [GeometryType.Point]: undefined,
  [GeometryType.Line2]: undefined,
  [GeometryType.Line3D]: undefined,
  [GeometryType.LineStrip]: undefined,
  [GeometryType.LineLoop]: undefined,
  [GeometryType.Polyline]: undefined,
  [GeometryType.Polyline3D]: undefined,
  [GeometryType.AddIcon]: undefined,
  [GeometryType.Pointer]: undefined,
  [GeometryType.Function]: Function,
  [GeometryType.Vector]: Vector,
  [GeometryType.Slider]: undefined,
  [GeometryType.Variable]: undefined,
  [GeometryType.Invalid]: undefined,
}

class ShapeGenerator {
  create(
    type: GeometryType,
    vertex: CreateParams,
    options?: Partial<GeometryShape> | any,
  ) {
    const shapeClass = shapeMap[type]

    if (!shapeClass) return null
    return new shapeClass(options).create(vertex)
  }
}
export default new ShapeGenerator()
